/*
  spindle_control.c - spindle control methods

  Part of grblHAL

  Copyright (c) 2017-2024 Terje Io
  Copyright (c) 2012-2015 Sungeun K. Jeon
  Copyright (c) 2009-2011 Simen Svale Skogsrud

  grblHAL is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  grblHAL is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
*/

#include <math.h>
#include <string.h>

#include "hal.h"
#include "protocol.h"
#include "state_machine.h"
#include "settings.h"

#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif

/*! \brief Structure for holding spindle registration data */
typedef struct {
    const spindle_ptrs_t *cfg;
    spindle_ptrs_t hal;
    const char *name;
    bool init_ok;
} spindle_reg_t;

/*! \brief Structure for holding data about an enabled spindle */
typedef struct {
    // hal中的param指针会指向现在这个param实体
    // param的hal指针会指向这个 hal实体
    spindle_param_t param;
    spindle_ptrs_t hal;
    bool enabled;
} spindle_sys_t;

// 记录注册了几根轴
static uint8_t n_spindle = 0;

// 这里面存放着已经选择激活的主轴
static spindle_sys_t sys_spindle[N_SYS_SPINDLE] = {0};
// spindles - 主轴数组,每一个元素都存储着一根主轴的信息
// pwm_spindle - 指向最先注册的PWM主轴
static spindle_reg_t spindles[N_SPINDLE] = {0}, *pwm_spindle = NULL;
static const spindle_data_ptrs_t *encoder;

/*! \internal \brief Activates and registers a spindle as enabled with a specific spindle number.
\param spindle_id spindle id of spindle to activate as a \ref spindle_id_t.
\param spindle_num spindle number to set as enabled as a \ref spindle_num_t.
\returns \a true if succsesful, \a false if not.
*/
// 激活一根主轴, 第一根注册的PWM主轴有最高优先权
// spindle_id 指定要激活注册列表中的哪根主轴
// spindle_num 指定激活的主轴在激活列表中的位置
static bool spindle_activate (spindle_id_t spindle_id, spindle_num_t spindle_num)
{
    bool ok;
    spindle_reg_t *spindle;

    // Always configure PWM spindle on startup to ensure outputs are set correctly.
    // pwm_spindle->cfg->config - 指向了被注册主轴的config函数,里面有硬件外设的初始化操作
    // 传入一个主轴的索引,该主轴不是第一个注册的PWM主轴,而且存在一个PWM主轴,并且提供了PWM主轴的初始化函数,条件成立
    // 这个操作会让PWM主轴优先被激活
    if(pwm_spindle && pwm_spindle->cfg->config && pwm_spindle != &spindles[spindle_id]) {

        if(!pwm_spindle->hal.cap.rpm_range_locked) { //还没有设置主轴的转速范围,从setting中加载转速范围
            pwm_spindle->hal.rpm_min = settings.spindle.rpm_min;
            pwm_spindle->hal.rpm_max = settings.spindle.rpm_max;
        }
        // pwm_spindle->hal.config == NULL 这里保证PWM主轴有有效的配置函数, 如果无效, pwm_spindle->hal.config(&pwm_spindle->hal)这部分就不执行了
        // 将 pwm_spindle->hal结构体传入到 pwm_spindle->hal.config中,这意味着在注册主轴的时候
        //      static const spindle_ptrs_t spindle = {
        //     .type = SpindleType_PWM,
        //     .cap.variable = On,
        //     .cap.laser = On,
        //     .cap.direction = On,
        //     .config = spindleConfig,
        //     .get_pwm = spindleGetPWM,
        //     .update_pwm = spindle_set_speed,
        //     .set_state = spindleSetState,
        //     .get_state = spindleGetState
        // };
        // 这样一个结构会传递给这个结构体成员中的 .config = spindleConfig
        // 保证配置函数配置的主轴是 spindle 描述的那根
        if((pwm_spindle->init_ok = pwm_spindle->hal.config == NULL || pwm_spindle->hal.config(&pwm_spindle->hal)))
        // 这里的 &pwm_spindle->hal 跟上面一样，也是保证调用的是 .set_state = spindleSetState 
        // 然后主轴的状态设置默认状态, 且目前的转速设置为0
            pwm_spindle->hal.set_state(&pwm_spindle->hal, (spindle_state_t){0}, 0.0f);
    }
    pwm_spindle = NULL;// PWM主轴被激活之后,不再给它做特殊标记

    // 检查主轴索引有没有超出范围且该索引指向的主轴有没有被注册
    if((ok = spindle_id >= 0 && spindle_id < n_spindle && !!spindles[spindle_id].cfg)) {

        spindle = &spindles[spindle_id];

        // 当前如果注册了一根已经被激活的主轴,把所有激活的主轴都停了,但是这不会导致激活动作失效
        if(sys_spindle[spindle_num].enabled && sys_spindle[spindle_num].hal.id != spindle_id && sys_spindle[spindle_num].hal.set_state)
            gc_spindle_off(); // TODO: switch off only the default spindle?

        if(!spindle->hal.cap.rpm_range_locked) {//还没有设置主轴的转速范围,从setting中加载转速范围
            spindle->hal.rpm_min = settings.spindle.rpm_min;
            spindle->hal.rpm_max = settings.spindle.rpm_max;
        }

        if( !spindle->init_ok )// 首次激活这里应该是成立的
            // 下面这种 “||” 运算的目的还是为了先检测有没有有效的config函数, 然后再执行
            ok = spindle->init_ok = spindle->hal.config == NULL || spindle->hal.config(&spindle->hal);

        if(ok) {// 主轴的配置函数成功执行了

            spindle_ptrs_t spindle_hal;

            memcpy(&spindle_hal, &spindle->hal, sizeof(spindle_ptrs_t));

            // TODO : 涉及settings模块,跟激光主轴有关
            // 当前主轴是激光器而且GRBL选择的是激光模式
            spindle_hal.cap.laser &= settings.mode == Mode_Laser;

            if(grbl.on_spindle_select) // 这个指针是没有初始化的,这是一个钩子函数,如果想对目前激活的主轴做点什么操作,可以通过这个钩子操作
                ok = grbl.on_spindle_select(&spindle_hal);

            if(ok) { // 主轴成功激活而且钩子成功执行(如果有的话)
                sys_spindle[spindle_num].enabled = true;

                // sys_spindle[spindle_num].param.hal指向 sys_spindle[spindle_num].hal实体
                // spindle_hal 作为一个中间变量 spindle_hal.param 指向 sys_spindle[spindle_num].param参数实体
                // sys_spindle[spindle_num].hal 实体 复制 spindle_hal
                // 这种设计的关键是数据和控制分离，通过将配置参数 (param) 和硬件控制接口 (hal) 分开管理,但是实际上它们是强耦合的
                sys_spindle[spindle_num].param.hal = &sys_spindle[spindle_num].hal;
                if(sys_spindle[spindle_num].param.override_pct == 0) // 主轴转速的百分比,默认DEFAULT_SPINDLE_RPM_OVERRIDE:100
                    sys_spindle[spindle_num].param.override_pct = DEFAULT_SPINDLE_RPM_OVERRIDE;
                spindle_hal.param = &sys_spindle[spindle_num].param;
                memcpy(&sys_spindle[spindle_num].hal, &spindle_hal, sizeof(spindle_ptrs_t));
                if(grbl.on_spindle_selected) // 钩子函数
                    grbl.on_spindle_selected(&sys_spindle[spindle_num].hal);
// TODO : 主轴大于1的状态先略过
#if N_SPINDLE > 1
                system_add_rt_report(Report_SpindleId);
#endif
            }
        }
    }

    return ok;
}

/*! \brief Register a spindle with the core.
\param spindle pointer to a \a spindle_ptrs_t structure.
\param name pointer to a null terminated string.
\returns assigned \a spindle id as a \ref spindle_id_t if successful, -1 if not.

__NOTE:__ The first spindle registered will become the default active spindle.
__NOTE:__ up to \ref N_SPINDLE spindles can be registered at a time.
*/
// 注册一根主轴
spindle_id_t spindle_register (const spindle_ptrs_t *spindle, const char *name)
{
    // n_spindle 是主轴编号器,没注册一个根主轴就向上累加1
    // 下面这个操作是在注册第二根主轴的时候检查一下第一根主轴的类型
    // 第一根主轴的类型必须是非 SpindleType_Null 否则会被新注册的主轴顶替掉
    if(n_spindle == 1 && spindles[0].cfg->type == SpindleType_Null)
        n_spindle = 0;

    if(n_spindle < N_SPINDLE && settings_add_spindle_type(name)) {// 检查一下主轴数量有没有超标,缓存主轴名字的内存是否存储新主轴的名字
        // 备份主轴的信息
        spindles[n_spindle].cfg = spindle; // 保存新主轴的描述结构体的地址
        spindles[n_spindle].name = name;
        memcpy(&spindles[n_spindle].hal, spindles[n_spindle].cfg, sizeof(spindle_ptrs_t));
        spindles[n_spindle].hal.id = n_spindle; // 这里面存放的是索引号

        if(spindle->type == SpindleType_PWM && pwm_spindle == NULL) {
            pwm_spindle = &spindles[n_spindle];
            hal.driver_cap.pwm_spindle = On;
        }

        if(n_spindle == 0) // 再次确保sys_spindle[0]号位置被优先注册
            memcpy(&sys_spindle[0].hal, spindle, sizeof(spindle_ptrs_t));

        return n_spindle++;
    }

    return -1;
}

/*! \brief Enable a spindle and make it available for use by gcode.
\param spindle_id spindle id as a \ref spindle_id_t.
\returns assigned spindle number as a \a spindle_num_t if successful \a -1 if not.

__NOTE:__ up to \ref N_SYS_SPINDLE spindles can be enabled at a time.
*/
// 这个函数是对 spindle_activate 的封装,实际上是在激活一根主轴
spindle_num_t spindle_enable (spindle_id_t spindle_id)
{
    uint_fast8_t idx = 0;
    spindle_num_t spindle_num = -1;

    if(spindle_id >= 0 && spindle_id < n_spindle) do {
        if(!sys_spindle[idx].enabled && spindle_activate(spindle_id, idx))
            spindle_num = idx;
    } while(++idx < N_SYS_SPINDLE && spindle_num == -1);

    return spindle_num;
}

/*! \brief Enables a spindle and sets it as default spindle (spindle number 0).
\param spindle_id spindle id as a \ref spindle_id_t.
\returns \a true if succsesful, \a false if not.
*/
// 在主轴列表中选择一根放在激活列表的首个位置
bool spindle_select (spindle_id_t spindle_id)
{
    if(n_spindle == 0 && spindle_id >= 0) {
        spindle_id = 0;
        spindle_add_null();
    }

    return (sys_spindle[0].enabled && sys_spindle[0].hal.id == spindle_id) || spindle_activate(spindle_id, 0);
}


/*! \brief Get the handlers (function pointers) etc. associated with the spindle.
\param spindle_id spindle id as a \ref spindle_id_t.
\param hal a \ref spindle_hal_t enum value:
<br>\ref SpindleHAL_Raw - get the read only version as supplied at registration
<br>\ref SpindleHAL_Configured - get the version with run-time modifications applied by the spindle driver.
<br>\ref SpindleHAL_Active - get the enabled version available from gcode. Can be overriden by event handlers prior to activation.
\returns pointer to a \ref spindle_ptrs_t structure if successful, \a NULL if not.

__NOTE:__ do not modify the returned structure!
*/
// 返回主轴的配置结构体 它可能来自主轴的注册列表或者激活列表
spindle_ptrs_t *spindle_get_hal (spindle_id_t spindle_id, spindle_hal_t hal)
{
    spindle_ptrs_t *spindle = NULL;

    if(hal == SpindleHAL_Active) {

        uint_fast8_t idx = N_SYS_SPINDLE;

        do {
            idx--;
            if(sys_spindle[idx].hal.id == spindle_id && sys_spindle[idx].enabled)
                spindle = &sys_spindle[idx].hal; // 这是激活列表
        } while(idx && spindle == NULL);

    } else if(spindle_id >= 0 && spindle_id < n_spindle && spindles[spindle_id].cfg)
    // spindles[spindle_id].cfg 中存储着最原始的主轴信息，这些信息可能来自driver.c中
        spindle = hal == SpindleHAL_Raw ? (spindle_ptrs_t *)spindles[spindle_id].cfg : &spindles[spindle_id].hal;

    return spindle;
}

/*! \brief Get the spindle id of the default spindle (spindle number 0).
\returns spindle id as a \ref spindle_id_t if successful, \a -2 if not (no spindle available).
*/
// 返回主轴激活列表中的第一根主轴的ID(0)如果这根主轴没激活,返回-2
spindle_id_t spindle_get_default (void)
{
    return sys_spindle[0].enabled ? sys_spindle[0].hal.id : -2;
}

/*! \brief Get the merged spindle capabilities of all registered spindles.
\param active true to return active capabilities, false to return default capabilities.
\returns capabilities in a \ref spindle_cap_t structure.
*/
// 返回主轴的能力,比如方向,是否可变转速,是不是激光主轴什么的
// 是全部主轴的的能力,不是单根主轴的能力,反应机床全体主轴的全部的功能
spindle_cap_t spindle_get_caps (bool active)
{
    spindle_cap_t caps = {0};
    uint_fast8_t idx = n_spindle;

    if(n_spindle) do {
        --idx;
        // cfg - 指向注册的时候传递给注册函数的最原始的那个主轴结构体
        // hal - 复制了一份cfg指向的主轴结构体
        caps.value |= (active ? spindles[idx].hal.cap.value : spindles[idx].cfg->cap.value);
    } while(idx);

    return caps;
}

/*! \brief Get the registered name of a spindle.
\param spindle_id spindle id as a \ref spindle_id_t.
\returns pointer to a null terminated string if succesful, \a NULL if not.
*/
// 返回主轴的名字
const char *spindle_get_name (spindle_id_t spindle_id)
{
    return spindle_id >= 0 && spindle_id < n_spindle && spindles[spindle_id].cfg ? spindles[spindle_id].name : NULL;
}

/*! \brief Update the capabilities of a registered PWM spindle.
May be used by the driver on spindle initialization or when spindle settings has been changed.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param pwm_caps pointer to a \ref spindle_pwm_t structure.
*/
// 更新主轴的能力, 可更该已经注册到列表中的主轴
// spindle - 指向需要变更的主轴
// pwm_caps - 指向便跟的属性,比如转速从500该为1000
// 调用这个函数，主轴的类型会被设置为 SpindleType_PWM 或者 SpindleType_Basic
void spindle_update_caps (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_caps)
{
    uint_fast8_t idx = N_SYS_SPINDLE;  // 从系统中所有主轴的数量开始
    // 设置主轴类型为 PWM 或 Basic，根据传入的 pwm_caps 是否有效
    spindle->type = pwm_caps ? SpindleType_PWM : SpindleType_Basic;
    // 如果 pwm_caps 存在且 laser_mode_disable 标志位没有被禁用，且 update_pwm 不为空，且设置的模式为 Laser，则设置激光功能
    spindle->cap.laser = !!pwm_caps && !pwm_caps->flags.laser_mode_disable && !!spindle->update_pwm && settings.mode == Mode_Laser;
    // 如果 pwm_caps 存在，则设置 PWM 关闭值，否则为 0
    spindle->pwm_off_value = pwm_caps ? pwm_caps->off_value : 0;
    // 遍历所有系统中的主轴，找到当前激活的主轴并更新它的相关属性
    do {
        idx--;  // 循环遍历系统中的主轴
        if(sys_spindle[idx].enabled && spindle->id == sys_spindle[idx].hal.id) {  // 找到对应的主轴
            // 更新主轴的类型、激光能力、转速范围和 PWM 关闭值等信息
            sys_spindle[idx].hal.type = spindle->type;
            sys_spindle[idx].hal.cap.laser = spindle->cap.laser;
            sys_spindle[idx].hal.rpm_min =  spindle->rpm_min;
            sys_spindle[idx].hal.rpm_max =  spindle->rpm_max;
            sys_spindle[idx].hal.pwm_off_value =  spindle->pwm_off_value;
            break;  // 找到并更新后退出循环
        }
    } while(idx);  // 遍历所有主轴，直到找到匹配的主轴
}


/*! \brief Get number of registered spindles.
\returns number of registered spindles.
*/
// 返回主轴注册列表中的有多少根主轴
uint8_t spindle_get_count (void)
{
    if(n_spindle == 0)
        spindle_select(0);

    return n_spindle == 1 && spindles[0].cfg->type == SpindleType_Null ? 0 : n_spindle;
}
// 这函数是作用是验证 输入主轴列表索引的合法性,合法返回值 = spindle_id,不合法返回-1
static spindle_num_t spindle_get_num (spindle_id_t spindle_id)
{
    spindle_num_t spindle_num;
    // 如果系统中只有一个主轴，直接返回编号 0
    // 这个if 有点绕 : 如果没有注册主轴 spindle_num = -1 下面的循环不会改变这个spindle_num，如果轴不止一根，循环起作用 spindle_num会改变成检索到的主轴
    if((spindle_num = spindle_get_count() == 1 ? 0 : -1) == -1) {
        // 定义一个配置项指针
        const setting_detail_t *setting;
        uint_fast8_t idx = N_SPINDLE_SELECTABLE;  // 从可选择的主轴数开始
        // 遍历主轴设置项，寻找与 spindle_id 匹配的主轴编号
        do {
            idx--;  // 索引递减
            // 获取当前设置项的详细信息，逐个检查
            // 这个位置跟 $395 有关,暂时先不管
            if((setting = setting_get_details(idx == 0 ? Setting_SpindleType : (setting_id_t)(Setting_SpindleEnable0 + idx), NULL))) {
                // 如果设置项的值与当前索引匹配，则找到了对应的主轴
                if(setting_get_int_value(setting, 0) - (idx == 0 ? 0 : 1) == spindle_id)
                    spindle_num = idx;  // 找到对应的主轴编号
            }
        } while(idx && spindle_num == -1);  // 继续遍历，直到找到主轴编号或遍历结束
    }
    return spindle_num;  // 返回主轴编号
}
// 这个函数 spindle_bind_encoder 的目的是将传入的编码器数据(encoder_data)与主轴进行绑定
// 并更新主轴的相关功能。如果 encoder_data 被传入，且当前主轴的编号符合特定条件(比如 settings.offset_lock.encoder_spindle),
// 则将主轴的获取数据和重置数据的操作函数绑定为 encoder_data 中的相关函数；否则，使用配置文件中的默认设置。
void spindle_bind_encoder (const spindle_data_ptrs_t *encoder_data)
{
    uint_fast8_t idx;
    spindle_ptrs_t *spindle;
    spindle_num_t spindle_num;
    // 将传入的 encoder_data 保存到 encoder 变量中
    encoder = encoder_data;
    // 遍历所有的主轴
    for(idx = 0; idx < n_spindle; idx++) {
        // 获取当前主轴的编号和相关数据
        spindle = spindle_get((spindle_num = spindle_get_num(idx)));
        // 如果传入了 encoder_data 且当前主轴是配置的 encoder 主轴
        // TODO : settings.offset_lock.encoder_spindle 的设置有些不全面
        if(encoder_data && spindle_num == settings.offset_lock.encoder_spindle) {
            // 将当前主轴的 get_data 和 reset_data 设置为 encoder_data 中的函数
            spindles[idx].hal.get_data = encoder_data->get;
            spindles[idx].hal.reset_data = encoder_data->reset;
            // 设置主轴的 at_speed 能力为 variable，即可变速度的能力
            spindles[idx].hal.cap.at_speed = spindles[idx].hal.cap.variable;
        } else {
            // 如果没有传入 encoder_data，或者当前主轴不是配置的 encoder 主轴
            // 则使用默认的配置文件中的设置函数
            spindles[idx].hal.get_data = spindles[idx].cfg->get_data;
            spindles[idx].hal.reset_data = spindles[idx].cfg->reset_data;
            spindles[idx].hal.cap.at_speed = spindles[idx].cfg->cap.at_speed;
        }
        // 如果 spindle 指针有效，更新主轴的相关数据
        if(spindle) {
            spindle->get_data = spindles[idx].hal.get_data;
            spindle->reset_data = spindles[idx].hal.reset_data;
            spindle->cap.at_speed = spindles[idx].hal.cap.at_speed;
        }
    }
}

// 这个函数 spindle_set_at_speed_range 的主要作用是根据传入的转速（rpm）设置主轴的数据结构中与转速相关的参数，尤其是转速的容忍范围
bool spindle_set_at_speed_range (spindle_ptrs_t *spindle, spindle_data_t *spindle_data, float rpm)
{
    // 设置目标转速
    spindle_data->rpm_programmed = rpm;
    // 初始状态下，假设主轴还没有达到目标转速
    spindle_data->state_programmed.at_speed = false;
    // 如果主轴的转速容忍度大于0，则启用“在速”检测
    if((spindle_data->at_speed_enabled = spindle->at_speed_tolerance > 0.0f)) {
        // 计算目标转速的容忍范围
        spindle_data->rpm_low_limit = rpm * (1.0f - (spindle->at_speed_tolerance / 100.0f));  // 下限转速
        spindle_data->rpm_high_limit = rpm * (1.0f + (spindle->at_speed_tolerance / 100.0f)); // 上限转速
    }
    // 返回是否启用了“在速”检测
    return spindle_data->at_speed_enabled;
}

/*! \brief Enumerate registered spindles by calling a callback function for each of them.
\param callback pointer to a \ref spindle_enumerate_callback_ptr type function.
\param data pointer to optional data to pass to the callback function.
\returns \a true if spindles are registered and a callback function was provided, \a false otherwise.
*/
// 这里是遍历整个主轴注册列表,然后传入到回调函数中
bool spindle_enumerate_spindles (spindle_enumerate_callback_ptr callback, void *data)
{
    if(callback == NULL || n_spindle == 0)
        return false;

    uint_fast8_t idx;
    spindle_info_t spindle;

    for(idx = 0; idx < n_spindle; idx++) {

        spindle.id = idx;
        spindle.ref_id = spindles[idx].cfg->ref_id;
        spindle.name = spindles[idx].name;
        spindle.num = spindle_get_num(idx);
        spindle.enabled = spindle.num != -1;
        spindle.hal = spindle.enabled && sys_spindle[spindle.num].hal.id == spindle.id ? &sys_spindle[spindle.num].hal : &spindles[idx].hal;
        spindle.is_current = spindle.enabled && sys_spindle[0].hal.id == idx;

        if(callback(&spindle, data))
            return true;
    }

    return false;
}

// The following calls uses logical spindle numbers pointing into the sys_spindle array
// containing enabled spindles (spindle number as used by the $ gcode word)

/*! \brief Check if a spindle is enabled and available or not.
\param spindle_num spindle number as a \ref spindle_num_t.
\returns \a true if the spindle is enabled, \a false otherwise.
*/
// 检测 spindle_num 是不是已经激活了
bool spindle_is_enabled (spindle_num_t spindle_num)
{
    if(spindle_num == -1)
        spindle_num = 0;

    return spindle_num >= 0 && spindle_num < N_SYS_SPINDLE && sys_spindle[spindle_num].enabled;
}

/*! \brief Get the handlers (function pointers) etc. associated with an enabled spindle.
\param spindle_num spindle number as a \ref spindle_num_t.
\returns pointer to a \ref spindle_ptrs_t structure if successful, \a NULL if not.

__NOTE:__ do not modify the returned structure!
*/
// 获取已经激活的主轴的函数结构体 ( static const spindle_ptrs_t spindle )
spindle_ptrs_t *spindle_get (spindle_num_t spindle_num)
{
    return spindle_num >= 0 && spindle_num < N_SYS_SPINDLE && sys_spindle[spindle_num].enabled ? &sys_spindle[spindle_num].hal : NULL;
}

//

//
// Null (dummy) spindle, automatically installed if no spindles are registered.
//
// 下面这些null_开头的函数是用来占位的
static void null_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
{
    UNUSED(spindle);
    UNUSED(state);
    UNUSED(rpm);
}

static spindle_state_t null_get_state (spindle_ptrs_t *spindle)
{
    UNUSED(spindle);

    return (spindle_state_t){0};
}

// Sets spindle speed
static void null_update_pwm (spindle_ptrs_t *spindle, uint_fast16_t pwm_value)
{
    UNUSED(spindle);
    UNUSED(pwm_value);
}

static uint_fast16_t null_get_pwm (spindle_ptrs_t *spindle, float rpm)
{
    UNUSED(spindle);
    UNUSED(rpm);

    return 0;
}

static void null_update_rpm (spindle_ptrs_t *spindle, float rpm)
{
    UNUSED(spindle);
    UNUSED(rpm);
}

#ifdef GRBL_ESP32
static void null_esp32_off (spindle_ptrs_t *spindle)
{
    UNUSED(spindle);
}
#endif

/*! \brief Register a null spindle that has no connection to the outside world.
This is done automatically on startup if no spindle can be succesfully enabled.
\returns assigned spindle id as a \ref spindle_id_t if successful, \a -1 if not.
*/
// 注册一根虚拟主轴,这个根主轴不对应任何实体主轴
spindle_id_t spindle_add_null (void)
{
    static const spindle_ptrs_t spindle = {
        .type = SpindleType_Null,
        .cap.variable = Off,
        .cap.at_speed = Off,
        .cap.direction = Off,
        .set_state = null_set_state,
#ifdef GRBL_ESP32
        .esp32_off = null_esp32_off,
#endif
        .get_state = null_get_state,
        .get_pwm = null_get_pwm,
        .update_pwm = null_update_pwm,
        .update_rpm = null_update_rpm
    };

    bool registered = false;
    uint_fast8_t idx = n_spindle;

    if(idx) do {
        if((registered = spindles[--idx].hal.type == SpindleType_Null))
            break;
    } while(idx);

    if(!registered)
        return spindle_register(&spindle, "NULL");

    return idx;
}

// End null (dummy) spindle.

/*! \brief Set spindle speed override.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param speed_override override as a percentage of the programmed RPM.

__NOTE:__ Unlike motion overrides, spindle overrides do not require a planner reinitialization.
*/
// 暂时先略过
void spindle_set_override (spindle_ptrs_t *spindle, override_t speed_override)
{
//    if(speed_override != 100 && sys.override.control.spindle_rpm_disable)
//        return;
    // spindle->param->state.override_disable这个参数的源头在gcode.c里面,但是它的作用是标记是否允许覆盖主轴的速度
    if(speed_override != 100 && spindle->param->state.override_disable)
        return;

    speed_override = constrain(speed_override, MIN_SPINDLE_RPM_OVERRIDE, MAX_SPINDLE_RPM_OVERRIDE);

    if((uint8_t)speed_override != spindle->param->override_pct) {

        spindle_set_rpm(spindle, spindle->param->rpm, speed_override);

        if(state_get() == STATE_IDLE) {
            if(spindle->get_pwm && spindle->update_pwm)
                spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden));
            else if(spindle->update_rpm)
                spindle->update_rpm(spindle, spindle->param->rpm_overridden);
        } else
            sys.step_control.update_spindle_rpm = On;

        system_add_rt_report(Report_Overrides); // Set to report change immediately

//       if(grbl.on_spindle_programmed)
//           grbl.on_spindle_programmed(spindle, spindle->param->state, spindle->param->rpm, spindle->param->rpm_mode);

       if(grbl.on_override_changed)
           grbl.on_override_changed(OverrideChanged_SpindleRPM);
    }
}

/*! \internal \brief Immediately sets spindle running state with direction and spindle rpm, if enabled.
Called by g-code parser spindle_sync(), parking retract and restore, g-code program end,
sleep, and spindle stop override.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param state a \ref spindle_state_t structure.
\param rpm the spindle RPM to set.
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
*/
static bool set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
{
    // 当前取消和终止两个系统状态标志位没有生效
    if (!ABORTED) { // Block during abort.
        // 主轴没有开启,调用主轴描述结构体中的设置函数，把主轴设置成默认状态
        if (!state.on) { // Halt or set spindle direction and rpm.
            rpm = 0.0f;
            spindle->set_state(spindle, (spindle_state_t){0}, 0.0f);
        } else {
            // NOTE: Assumes all calls to this function is when grblHAL is not moving or must remain off.
            // TODO: alarm/interlock if going from CW to CCW directly in non-laser mode?
            // 如果是激光主轴而且设置了反转,转速强制为0
            if (spindle->cap.laser && state.ccw)
                rpm = 0.0f; // TODO: May need to be rpm_min*(100/MAX_SPINDLE_RPM_OVERRIDE);
            // 参数传入到主轴的实际操作接口里面并执行
            spindle->set_state(spindle, state, spindle_set_rpm(spindle, rpm, spindle->param->override_pct));
        }

        spindle->param->rpm = rpm;
        spindle->param->state = state;

        system_add_rt_report(Report_Spindle); // Set to report change immediately

        st_rpm_changed(rpm);
    }

    return !ABORTED;
}


/*! \brief Immediately sets spindle running state with direction and spindle rpm, if enabled.
Called by g-code parser spindle_sync(), parking retract and restore, g-code program end,
sleep, and spindle stop override.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param state a \ref spindle_state_t structure.
\param rpm the spindle RPM to set.
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
*/
// 搜索一下这个函数在工程中是怎么应用的,大概知道它会传一个主轴的描述结构体,要设置的一些主轴的状态,和一个转速
bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
{
    return set_state(spindle, state, rpm);
}

/*! \brief G-code 解析器入口点，用于设置主轴状态。强制同步规划器缓冲区，如果处于中止或检查模式，则退出。
    如果主轴支持转速到达功能，则会等待主轴达到设定转速，并在超时未达到转速时触发报警。
    \param spindle 指向 \ref spindle_ptrs_t 结构的指针，表示当前主轴的参数和控制信息。
    \param state 主轴状态，类型为 \ref spindle_state_t。
    \param rpm 要设置的主轴转速。
    \returns \a true 如果成功，\a false 如果当前控制器状态为 \ref ABORTED。
*/
// 这个函数被执行,会等待主轴到达目标速度，到达目标速度后退出或者在规定的时间内没有达到转速要求，直接退出
bool spindle_sync (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
{
    bool ok;
    // 如果当前状态不是检查模式，继续执行
    if (!(ok = state_get() == STATE_CHECK_MODE)) {
        // 判断主轴是否需要等待达到转速。若不需要（例如没有转速到达功能，或者转速容忍度小于等于0），直接跳过等待。
        bool at_speed = !state.on || !spindle->cap.at_speed || spindle->at_speed_tolerance <= 0.0f;
        // 同步协议缓冲区，确保主轴设置与程序一致
        if((ok = protocol_buffer_synchronize()) && set_state(spindle, state, rpm) && !at_speed) {
            float on_delay = 0.0f;
            // 如果主轴需要等待达到转速，则进入循环检测
            while(!(at_speed = spindle->get_state(spindle).at_speed)) {
                // 每隔 0.2 秒检查一次主轴状态，是否达到设定转速
                if(!(ok = delay_sec(0.2f, DelayMode_Dwell)))
                    break;
                on_delay += 0.2f;
                // 如果超出设定的安全门延迟时间，则关闭主轴并触发报警
                if(!(ok = on_delay < settings.safety_door.spindle_on_delay)) {
                    gc_spindle_off();  // 关闭主轴
                    system_raise_alarm(Alarm_Spindle);  // 触发主轴报警
                    break;
                }
            }
        }
        // 如果主轴达到设定转速，返回 true
        ok &= at_speed;
    }
    return ok;
}

/*! \brief Restore spindle running state with direction, enable, spindle RPM and appropriate delay.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param state a \ref spindle_state_t structure.
\param rpm the spindle RPM to set.
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
*/
// 这是恢复主轴到设定转速的函数,如果是激光器,认为是不需要过渡过程的理想主轴
// 机械主轴则根据情况,等待主轴完成加速过程达到设定的转速
bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
{
    bool ok = true;
    if(spindle->cap.laser) // 当主轴是激光模式时，忽略主轴启动的延迟，设置激光开关
        sys.step_control.update_spindle_rpm = On; // 启动激光的操作
    else { 
        // TODO: 这里可能还需要检查当前的主轴状态是否和要恢复的状态一致
        spindle_set_state(spindle, state, rpm);  // 设置主轴的状态和转速
        if(state.on) {  // 如果主轴是开启状态
            if((ok = !spindle->cap.at_speed))  // at-speed功能是指 主轴达到设定转速的状态，如果主轴不支持at-speed功能
                ok = delay_sec(settings.safety_door.spindle_on_delay, DelayMode_SysSuspend);  // 等待主轴转速达到目标转速，期间有延迟
            else if((ok == (spindle->at_speed_tolerance <= 0.0f))) {
                // 如果主轴支持at-speed，开始检查转速是否达到目标转速
                float delay = 0.0f;
                while(!(ok = spindle->get_state(spindle).at_speed)) {  // 如果主轴没有达到目标转速
                    if(!(ok = delay_sec(0.1f, DelayMode_SysSuspend)))  // 等待 0.1 秒
                        break;  // 如果等待超时则退出循环
                    delay += 0.1f;  // 累加延迟时间
                    if(!(ok = delay < settings.safety_door.spindle_on_delay)) {  
                        system_raise_alarm(Alarm_Spindle);  // 如果超过最大等待时间，触发报警
                        break;  
                    }
                }
            }
        }
    }
    return ok;  // 返回执行状态，成功返回true，失败返回false
}

/*! \brief Calculate and set programmed RPM according to override and max/min limits
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param rpm the programmed RPM.
\param override_pct override value in percent.
\returns the calulated RPM.
*/

float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t override_pct)
{
    // 按照 override_pct 的百分比设置 rpm 的大小
    // Gcode里指定了转速是800 但是操作人员希望到900,那通过 令override_pct=112.5 得到 800 * override_pct * 0.01  = 900
    if(override_pct != 100)
        rpm *= 0.01f * (float)override_pct; // Scale RPM by override value.

    // 防止设置超出主轴允许的范围
    rpm = rpm <= 0.0f ? 0.0f : constrain(rpm, spindle->rpm_min, spindle->rpm_max);

    // 更新覆盖速度和覆盖百分比
    spindle->param->rpm_overridden = rpm;
    spindle->param->override_pct = override_pct;

    return rpm;
}

/*! \brief Turn off all enabled spindles.
*/
// 把注册列表中的全体主轴设置成默认状态,覆盖参数也修改为0
void spindle_all_off (void)
{
    spindle_ptrs_t *spindle;
    uint_fast8_t spindle_num = N_SYS_SPINDLE;

    do {
        if((spindle = spindle_get(--spindle_num))) {
            spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
            spindle->param->state.value = 0;
#ifdef GRBL_ESP32
            spindle->esp32_off(spindle);
#else
            // .set_state = spindleSetState
            spindle->set_state(spindle, (spindle_state_t){0}, 0.0f);
#endif
        }
    } while(spindle_num);

    system_add_rt_report(Report_Spindle);
}

/*! \brief Check if any of the enabled spindles is running.
\returns \a true if a spindle is running, \a false otherwise.
*/
// 遍历整个主轴列表,如果有活跃的主轴就返回 1
bool spindle_is_on (void)
{
    bool on = false;

    spindle_ptrs_t *spindle;
    uint_fast8_t spindle_num = N_SYS_SPINDLE;
    do {
        if((spindle = spindle_get(--spindle_num)))
            on = spindle->get_state(spindle).on;
    } while(spindle_num && !on);

    return on;
}

//
// The following functions are not called by the core, may be called by driver code.
//

/*! \brief calculate inverted pwm value if configured
\param pwm_data pointer t a \a spindle_pwm_t structure.
\param pwm_value non inverted PWM value.
\returns the inverted PWM value to use.
*/
// 这是在计算脉冲间隔,也就是PWM的低电平时间
static inline uint_fast16_t invert_pwm (spindle_pwm_t *pwm_data, uint_fast16_t pwm_value)
{
    return pwm_data->invert_pwm ? pwm_data->period - pwm_value - 1 : pwm_value;
}

/*! \brief Spindle RPM to PWM conversion.
\param pwm_data pointer t a \a spindle_pwm_t structure.
\param rpm spindle RPM.
\param pid_limit boolean, \a true if PID based spindle sync is used, \a false otherwise.
\returns the PWM value to use.

__NOTE:__ \a spindle_precompute_pwm_values() must be called to precompute values before this function is called.
Typically this is done by the spindle initialization code.
*/
static uint_fast16_t spindle_compute_pwm_value (spindle_pwm_t *pwm_data, float rpm, bool pid_limit)
{
    uint_fast16_t pwm_value;

    // 下面两个公式的推导都是用了这个公式PWM = m * RPM + b
    // m是比例因子
    // b是偏移值
    if(rpm > pwm_data->rpm_min) {
      #if ENABLE_SPINDLE_LINEARIZATION
        // Compute intermediate PWM value with linear spindle speed model via piecewise linear fit model.
        uint_fast8_t idx = pwm_data->n_pieces;

        if(idx) {
            do {
                idx--;
                if(idx == 0 || rpm > pwm_data->piece[idx].rpm) {
                    //主轴在某些 RPM 范围内表现出非线性（如起步慢、负载变化大），则需要采用 这个 分段模型
                    // start: 这一段的PWM与RPM的比例因子，即PWM值如何随RPM增加。
                    // end: 这一段PWM的固定偏移量，通常是对非线性关系的一种校准补偿。
                    // 举例 :
                    // 低速区（1000-3000 RPM）：响应较慢，需要更高 PWM。
                    // 高速区（3000-6000 RPM）：响应正常，线性比例适用。

                    // 人为确定分段的比例因子和固定偏移
                    // 低速区（1000-3000 RPM）: start = 0.05;end = 5;pwm_gradient = 1.0
                    // 高速区（3000-6000 RPM）: start = 0.03;end = 0;pwm_gradient = 1.0
                    // 假设目标转速为 4000 RPM，对应高速区
                    // pwm_value = [(0.03*4000-0)*1.0] = 120
                    // 如果目标转速为 2000 RPM，对应低速区
                    // pwm_value = [(0.05*2000-5)*1.0] = 95
                    pwm_value = floorf((pwm_data->piece[idx].start * rpm - pwm_data->piece[idx].end) * pwm_data->pwm_gradient);
                    break;
                }
            } while(idx);
        } else
      #endif
        // Compute intermediate PWM value with linear spindle speed model.
        // 主轴的 RPM 和 PWM 是线性关系，使用这个即可满足需求
        // 举例:
        // 主轴 RPM 范围：1000 RPM 至 6000 RPM; PWM 输出范围：100 至 255（8 位精度）
        // pwm_gradient = (255-100)/(6000-1000) = 0.031
        // rpm_min = 1000
        // min_value = 100
        // 假设我们设定目标转速为 4000 RPM
        // pwm_value=[(4000−1000)*0.031]+100 = 193
        // floorf是向下取整
        pwm_value = (uint_fast16_t)floorf((rpm - pwm_data->rpm_min) * pwm_data->pwm_gradient) + pwm_data->min_value;

        if(pwm_value >= (pid_limit ? pwm_data->period : pwm_data->max_value))
            pwm_value = pid_limit ? pwm_data->period - 1 : pwm_data->max_value;
        else if(pwm_value < pwm_data->min_value)
            pwm_value = pwm_data->min_value;

        pwm_value = invert_pwm(pwm_data, pwm_value);// 脉冲间隔
    } else
        pwm_value = rpm == 0.0f ? pwm_data->off_value : invert_pwm(pwm_data, pwm_data->min_value);// 保持最小输出

    return pwm_value;
}

/*! \internal \brief Dummy spindle RPM to PWM conversion, used if precompute fails.
\param pwm_data pointer t a \a spindle_pwm_t structure.
\param rpm spindle RPM.
\param pid_limit boolean, \a true if PID based spindle sync is used, \a false otherwise.
\returns the PWM value to use.
*/
static uint_fast16_t compute_dummy_pwm_value (spindle_pwm_t *pwm_data, float rpm, bool pid_limit)
{
    return pwm_data->off_value;
}

/*! \brief Precompute PWM values for faster conversion.
\param spindle pointer to a \ref spindle_ptrs_t structure.
\param pwm_data pointer to a \a spindle_pwm_t structure, to hold the precomputed values.
\param clock_hz timer clock frequency used for PWM generation.
\returns \a true if successful, \a false if no PWM range possible - driver should then revert to simple on/off spindle control.
*/

/*! \brief 预计算PWM值以加快转换速度。
 * 该函数会根据主轴设置预先计算PWM相关的值，以便在实际运行时能更快速地计算出PWM值。
 * \param spindle 指向一个 \ref spindle_ptrs_t 结构体的指针，表示当前的主轴对象。
 * \param pwm_data 指向一个 \a spindle_pwm_t 结构体的指针，用于保存预计算的PWM值。
 * \param clock_hz 定时器时钟频率（用于PWM生成）。
 * \returns 如果成功返回 \a true，如果不支持PWM范围，则返回 \a false（此时驱动程序应该退回到简单的开/关主轴控制模式）。
 */
bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_data, spindle_settings_t *settings, uint32_t clock_hz)
{
    pwm_data->settings = settings;
    spindle->rpm_min = pwm_data->rpm_min = settings->rpm_min;
    spindle->rpm_max = settings->rpm_max;
    spindle->at_speed_tolerance = settings->at_speed_tolerance;
    spindle->cap.rpm_range_locked = On;

    // 如果主轴支持可变转速并且PWM功能没有禁用，则计算PWM相关参数
    if((spindle->cap.variable = !settings->flags.pwm_disable && spindle->rpm_max > spindle->rpm_min)) {
        pwm_data->f_clock = clock_hz;  // 设置时钟频率
        pwm_data->period = (uint_fast16_t)((float)clock_hz / settings->pwm_freq);  // 计算PWM周期

        // 如果关闭PWM，则设置PWM关闭值
        if(settings->pwm_off_value == 0.0f)
            pwm_data->off_value = pwm_data->invert_pwm ? pwm_data->period : 0;  // 根据是否反转设置off_value
        else
            pwm_data->off_value = invert_pwm(pwm_data, (uint_fast16_t)(pwm_data->period * settings->pwm_off_value / 100.0f));  // 计算PWM关闭值

        // 计算PWM最大值
        pwm_data->max_value = (uint_fast16_t)(pwm_data->period * settings->pwm_max_value / 100.0f) + pwm_data->offset;

        // 如果最小值为0且主轴的最小转速大于0，则设置最小值
        if((pwm_data->min_value = (uint_fast16_t)(pwm_data->period * settings->pwm_min_value / 100.0f)) == 0 && spindle->rpm_min > 0.0f)
            pwm_data->min_value = (uint_fast16_t)((float)pwm_data->max_value * 0.004f);  // 最小值为最大值的0.004倍

        // 计算PWM梯度
        pwm_data->pwm_gradient = (float)(pwm_data->max_value - pwm_data->min_value) / (spindle->rpm_max - spindle->rpm_min);

        pwm_data->always_on = settings->pwm_off_value != 0.0f;  // 判断是否始终开启PWM
        pwm_data->compute_value = spindle_compute_pwm_value;  // 设置计算PWM值的函数
    } else {
        pwm_data->off_value = 0;
        pwm_data->always_on = false;
        pwm_data->compute_value = compute_dummy_pwm_value;  // 如果不支持PWM，则使用虚拟计算函数
    }

    pwm_data->flags.invert_pwm = pwm_data->invert_pwm;  // 设置反转PWM标志
    pwm_data->flags.always_on = pwm_data->always_on;  // 设置始终开启标志
    pwm_data->flags.cloned = pwm_data->cloned;  // 设置克隆标志

    spindle->context.pwm = pwm_data;  // 将计算的PWM数据绑定到主轴上下文中

#if ENABLE_SPINDLE_LINEARIZATION
    uint_fast8_t idx;

    pwm_data->n_pieces = 0;

    // 如果有线性化的PWM分段，将其存入pwm_data
    for(idx = 0; idx < SPINDLE_NPWM_PIECES; idx++) {
        if(!isnan(settings->pwm_piece[idx].rpm) && settings->pwm_piece[idx].start != 0.0f)
            memcpy(&pwm_data->piece[pwm_data->n_pieces++], &settings->pwm_piece[idx], sizeof(pwm_piece_t));
    }

    spindle->cap.pwm_linearization = pwm_data->n_pieces > 0;  // 如果有线性化分段，则设置标志
#endif

    return spindle->cap.variable;  // 返回主轴是否支持可变转速
}

#if N_SPINDLE > 1

#include "grbl/nvs_buffer.h"

static spindle1_settings_t sp1_settings;
static uint32_t nvs_address;
static char spindle_signals[] = "Spindle enable,Spindle direction,PWM";
static bool ports_ok = false;
static char max_aport[4], max_dport[4];
static spindle_cap_t spindle_cap;
static spindle1_settings_changed_ptr on_settings_changed;

#if ENABLE_SPINDLE_LINEARIZATION

static status_code_t set_linear_piece (setting_id_t id, char *svalue)
{
    uint32_t idx = id - Setting_LinearSpindle1Piece1;
    float rpm, start, end;

    if(*svalue == '\0' || (svalue[0] == '0' && svalue[1] == '\0')) {
        sp1_settings.cfg.pwm_piece[idx].rpm = NAN;
        sp1_settings.cfg.pwm_piece[idx].start =
        sp1_settings.cfg.pwm_piece[idx].end = 0.0f;
    } else if(sscanf(svalue, "%f,%f,%f", &rpm, &start, &end) == 3) {
        sp1_settings.cfg.pwm_piece[idx].rpm = rpm;
        sp1_settings.cfg.pwm_piece[idx].start = start;
        sp1_settings.cfg.pwm_piece[idx].end = end;
//??       if(idx == 0)
//            sp1_settings.cfg.rpm_min = rpm;
    } else
        return Status_SettingValueOutOfRange;

    return Status_OK;
}

static char *get_linear_piece (setting_id_t id)
{
    static char buf[40];

    uint32_t idx = id - Setting_LinearSpindle1Piece1;

    if(isnan(sp1_settings.cfg.pwm_piece[idx].rpm))
        *buf = '\0';
    else
        snprintf(buf, sizeof(buf), "%g,%g,%g", sp1_settings.cfg.pwm_piece[idx].rpm, sp1_settings.cfg.pwm_piece[idx].start, sp1_settings.cfg.pwm_piece[idx].end);

    return buf;
}

#endif

static status_code_t set_spindle_invert (setting_id_t id, uint_fast16_t int_value)
{
    sp1_settings.cfg.invert.mask = int_value;
    if(sp1_settings.cfg.invert.pwm && !spindle_cap.pwm_invert) {
        sp1_settings.cfg.invert.pwm = Off;
        return Status_SettingDisabled;
    }

    return Status_OK;
}

static uint32_t get_int (setting_id_t id)
{
    uint32_t value = 0;

    switch(id) {

        case Setting_SpindleInvertMask1:
            value = sp1_settings.cfg.invert.value;
            break;

        default:
            break;
    }

    return value;
}

static status_code_t set_dir_port (setting_id_t id, float value)
{
    sp1_settings.port_dir = value < 0.0f ? 255 : (int8_t)value;

    return Status_OK;
}

static float get_dir_port (setting_id_t id)
{
    return sp1_settings.port_dir == 255 ? -1.0f : (float)sp1_settings.port_dir;
}

static uint32_t get_pwm_port (setting_id_t id)
{
    return (uint32_t)sp1_settings.port_pwm;
}

bool pwm_port_validate (xbar_t *properties, uint8_t port, void *data)
{
    return port == (uint8_t)((uint32_t)data);
}

static status_code_t set_pwm_port (setting_id_t id, uint_fast16_t int_value)
{
    bool ok;

    if((ok = (uint8_t)int_value == sp1_settings.port_pwm ||
              ioports_enumerate(Port_Analog, Port_Output, (pin_cap_t){ .pwm = On, .claimable = On }, pwm_port_validate, (void *)((uint32_t)int_value))))
        sp1_settings.port_pwm = (uint8_t)int_value;

    return ok ? Status_OK : Status_SettingValueOutOfRange;
}

static bool has_pwm (const setting_detail_t *setting)
{
    return spindle_cap.variable;
}

static bool has_freq (const setting_detail_t *setting)
{
    return spindle_cap.variable && !spindle_cap.cloned;
}

static bool has_ports (const setting_detail_t *setting)
{
    return ports_ok;
}

static const setting_detail_t spindle1_settings[] = {
    { Setting_Spindle_OnPort, Group_AuxPorts, "PWM2 spindle on port", NULL, Format_Int8, "##0", "0", max_dport, Setting_NonCore, &sp1_settings.port_on, NULL, has_ports, { .reboot_required = On } },
    { Setting_Spindle_DirPort, Group_AuxPorts, "PWM2 spindle direction port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_dir_port, get_dir_port, has_ports, { .reboot_required = On } },
    { Setting_SpindleInvertMask1, Group_Spindle, "Invert PWM2 spindle signals", NULL, Format_Bitfield, spindle_signals, NULL, NULL, Setting_IsExtendedFn, set_spindle_invert, get_int, NULL, { .reboot_required = On } },
    { Setting_Spindle_PWMPort, Group_AuxPorts, "PWM2 spindle PWM port", NULL, Format_Int8, "#0", "0", max_aport, Setting_NonCoreFn, set_pwm_port, get_pwm_port, has_ports, { .reboot_required = On } },
    { Setting_RpmMax1, Group_Spindle, "Maximum PWM2 spindle speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_max, NULL, has_pwm },
    { Setting_RpmMin1, Group_Spindle, "Minimum PWM2 spindle speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_min, NULL, has_pwm },
    { Setting_PWMFreq1, Group_Spindle, "PWM2 spindle PWM frequency", "Hz", Format_Decimal, "#####0", NULL, NULL, Setting_IsExtended, &sp1_settings.cfg.pwm_freq, NULL, has_freq },
    { Setting_PWMOffValue1, Group_Spindle, "PWM2 spindle PWM off value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_off_value, NULL, has_pwm },
    { Setting_PWMMinValue1, Group_Spindle, "PWM2 spindle PWM min value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_min_value, NULL, has_pwm },
    { Setting_PWMMaxValue1, Group_Spindle, "PWM2 spindle PWM max value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_max_value, NULL, has_pwm }
#if xENABLE_SPINDLE_LINEARIZATION
     { Setting_LinearSpindle1Piece1, Group_Spindle, "PWM2 spindle linearisation, 1st point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
  #if SPINDLE_NPWM_PIECES > 1
     { Setting_LinearSpindle1Piece2, Group_Spindle, "PWM2 spindle linearisation, 2nd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
  #endif
  #if SPINDLE_NPWM_PIECES > 2
     { Setting_LinearSpindle1Piece3, Group_Spindle, "PWM2 spindle linearisation, 3rd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
  #endif
  #if SPINDLE_NPWM_PIECES > 3
     { Setting_LinearSpindle1Piece4, Group_Spindle, "PWM2 spindle linearisation, 4th point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
  #endif
#endif
};

#ifndef NO_SETTINGS_DESCRIPTIONS
static const setting_descr_t spindle1_settings_descr[] = {
    { Setting_Spindle_OnPort, "On/off aux port." },
    { Setting_Spindle_DirPort, "Direction aux port, set to -1 if not required." },
    { Setting_SpindleInvertMask1, "Inverts the spindle on, counterclockwise and PWM signals (active low)." },
    { Setting_Spindle_PWMPort, "Spindle analog aux port. Must be PWM capable!" },
    { Setting_RpmMax1, "Maximum spindle speed." },
    { Setting_RpmMin1, "Minimum spindle speed." },
    { Setting_PWMFreq1, "PWM frequency." },
    { Setting_PWMOffValue1, "PWM off value in percent (duty cycle)." },
    { Setting_PWMMinValue1, "PWM min value in percent (duty cycle)." },
    { Setting_PWMMaxValue1, "PWM max value in percent (duty cycle)." }
#if xENABLE_SPINDLE_LINEARIZATION
     { Setting_LinearSpindle1Piece1, "Comma separated list of values: RPM_MIN, RPM_LINE_A1, RPM_LINE_B1, set to blank to disable." },
  #if SPINDLE_NPWM_PIECES > 1
     { Setting_LinearSpindle1Piece2, "Comma separated list of values: RPM_POINT12, RPM_LINE_A2, RPM_LINE_B2, set to blank to disable." },
  #endif
  #if SPINDLE_NPWM_PIECES > 2
     { Setting_LinearSpindle1Piece3, "Comma separated list of values: RPM_POINT23, RPM_LINE_A3, RPM_LINE_B3, set to blank to disable." },
  #endif
  #if SPINDLE_NPWM_PIECES > 3
     { Setting_LinearSpindle1Piece4, "Comma separated list of values: RPM_POINT34, RPM_LINE_A4, RPM_LINE_B4, set to blank to disable." },
  #endif
#endif
};
#endif

static void spindle1_settings_changed (settings_t *settings, settings_changed_flags_t changed)
{
    UNUSED(changed);

    if(on_settings_changed) {
        changed.spindle = On;
        on_settings_changed(&sp1_settings);
    }
}

static void spindle1_settings_save (void)
{
    hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&sp1_settings, sizeof(spindle1_settings_t), true);
}

static void spindle1_settings_restore (void)
{
    static const spindle_settings_t defaults = {
        .rpm_max = DEFAULT_SPINDLE1_RPM_MAX,
        .rpm_min = DEFAULT_SPINDLE1_RPM_MIN,
        .flags.pwm_disable = false,
        .flags.enable_rpm_controlled = 0, //DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED, TODO: add setting?
        .flags.laser_mode_disable = 0, // TODO: add setting? Not possible?
        .invert.on = DEFAULT_INVERT_SPINDLE1_ENABLE_PIN,
        .invert.ccw = DEFAULT_INVERT_SPINDLE1_CCW_PIN,
        .invert.pwm = DEFAULT_INVERT_SPINDLE1_PWM_PIN,
        .pwm_freq = DEFAULT_SPINDLE1_PWM_FREQ,
        .pwm_off_value = DEFAULT_SPINDLE1_PWM_OFF_VALUE,
        .pwm_min_value = DEFAULT_SPINDLE1_PWM_MIN_VALUE,
        .pwm_max_value = DEFAULT_SPINDLE1_PWM_MAX_VALUE,
        .at_speed_tolerance = DEFAULT_SPINDLE_AT_SPEED_TOLERANCE,
        .ppr = DEFAULT_SPINDLE_PPR,
        .pid.p_gain = DEFAULT_SPINDLE_P_GAIN,
        .pid.i_gain = DEFAULT_SPINDLE_I_GAIN,
        .pid.d_gain = DEFAULT_SPINDLE_D_GAIN,
        .pid.i_max_error = DEFAULT_SPINDLE_I_MAX,
#if ENABLE_SPINDLE_LINEARIZATION
  #if SPINDLE_NPWM_PIECES > 0
        .pwm_piece[0] = { .rpm = DEFAULT_RPM_POINT01, .start = DEFAULT_RPM_LINE_A1, .end = DEFAULT_RPM_LINE_B1 },
  #endif
  #if SPINDLE_NPWM_PIECES > 1
        .pwm_piece[1] = { .rpm = DEFAULT_RPM_POINT12, .start = DEFAULT_RPM_LINE_A2, .end = DEFAULT_RPM_LINE_B2 },
  #endif
  #if SPINDLE_NPWM_PIECES > 2
        .pwm_piece[2] = { .rpm = DEFAULT_RPM_POINT23, .start = DEFAULT_RPM_LINE_A3, .end = DEFAULT_RPM_LINE_B3 },
  #endif
  #if SPINDLE_NPWM_PIECES > 3
        .pwm_piece[3] = { .rpm = DEFAULT_RPM_POINT34, .start = DEFAULT_RPM_LINE_A4, .end = DEFAULT_RPM_LINE_B4 },
  #endif
#else
  #if SPINDLE_NPWM_PIECES > 0
        .pwm_piece[0] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
  #endif
  #if SPINDLE_NPWM_PIECES > 1
        .pwm_piece[1] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
  #endif
  #if SPINDLE_NPWM_PIECES > 2
        .pwm_piece[2] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
  #endif
  #if SPINDLE_NPWM_PIECES > 3
        .pwm_piece[3] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
  #endif
#endif
    };

    memcpy(&sp1_settings.cfg, &defaults, sizeof(spindle_settings_t));

    hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&sp1_settings, sizeof(spindle1_settings_t), true);
}

static void spindle1_settings_load (void)
{
    if((hal.nvs.memcpy_from_nvs((uint8_t *)&sp1_settings, nvs_address, sizeof(spindle1_settings_t), true) != NVS_TransferResult_OK))
        spindle1_settings_restore();
}

static setting_details_t spindle1_setting_details = {
    .settings = spindle1_settings,
    .n_settings = sizeof(spindle1_settings) / sizeof(setting_detail_t),
#ifndef NO_SETTINGS_DESCRIPTIONS
    .descriptions = spindle1_settings_descr,
    .n_descriptions = sizeof(spindle1_settings_descr) / sizeof(setting_descr_t),
#endif
    .load = spindle1_settings_load,
    .restore = spindle1_settings_restore,
    .save = spindle1_settings_save,
    .on_changed = spindle1_settings_changed
};

static bool pwm_count (xbar_t *properties, uint8_t port, void *data)
{
    *((uint32_t *)data) += 1;

    sp1_settings.port_pwm = max(sp1_settings.port_pwm, port);

    return false;
}

static bool check_pwm_ports (void)
{
    uint32_t n_pwm_out = 0;

    ioports_enumerate(Port_Analog, Port_Output, (pin_cap_t){ .pwm = On, .claimable = On }, pwm_count, &n_pwm_out);

    return n_pwm_out != 0;
}

spindle1_settings_t *spindle1_settings_add (bool claim_ports)
{
    if((ports_ok = claim_ports && hal.port.num_digital_out > 0 && check_pwm_ports())) {

        sp1_settings.port_on = hal.port.num_digital_out - 1;
        sp1_settings.port_dir = hal.port.num_digital_out > 1 ? hal.port.num_digital_out - 2 : 255;

        strcpy(max_aport, uitoa(sp1_settings.port_pwm));
        strcpy(max_dport, uitoa(hal.port.num_digital_out - 1));
    }

    return nvs_address == 0 && (!claim_ports || ports_ok) && (nvs_address = nvs_alloc(sizeof(spindle1_settings_t))) ? &sp1_settings : NULL;
}

void spindle1_settings_register (spindle_cap_t cap, spindle1_settings_changed_ptr on_changed)
{
    on_settings_changed = on_changed;

    settings_register(&spindle1_setting_details);

    spindle_cap = cap;

    spindle_state_t spindle_state = { .on = On };
    spindle_state.ccw = cap.direction;
    spindle_state.pwm = cap.pwm_invert;

    setting_remove_elements(Setting_SpindleInvertMask1, spindle_state.mask);
}

#endif
